package com.zrs.gmall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zrs.gmall.common.cache.GmallCache;
import com.zrs.gmall.common.constant.MqConst;
import com.zrs.gmall.common.constant.RedisConst;
import com.zrs.gmall.common.service.RabbitService;
import com.zrs.gmall.product.mapper.SkuAttrValueMapper;
import com.zrs.gmall.product.mapper.SkuInfoMapper;
import com.zrs.gmall.product.mapper.SkuSaleAttrValueMapper;
import com.zrs.gmall.product.model.*;
import com.zrs.gmall.product.service.*;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author ZhaoRuiSheng
 * @create 2023/2/24 18:30
 */
@Slf4j
@Service
public class SkuManageServiceImpl implements SkuManageService {

    @Autowired
    private SkuInfoService skuInfoService;

    @Autowired
    private SkuImageService skuImageService;

    @Autowired
    private SkuAttrValueService skuAttrValueService;

    @Autowired
    private SkuSaleAttrValueService skuSaleAttrValueService;

    @Autowired
    private SkuInfoMapper skuInfoMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RabbitService rabbitService;

    /**
     * 保存商品SKU信息
     * 1.将前端提交商品SKU基本信息存入sku_info表
     * 2.将前端提交当前SKU相关图片集合 存入到sku_image表
     * 3.将前端提交当前SKU的平台属性信息 存入到 sku_attr_value表
     * 4.将前端提交当前SKU的销售属性信息 存入到 sku_sale_attr_value表
     *
     * @param skuInfo
     * @return
     */
    //写之前注意想流程，给表赋值之前，
    // 想清楚哪些前端传来的，表自增的，然后自己只需把剩余的数据存入表就可以了
    @Override
    public void saveSkuInfo(SkuInfo skuInfo) {
        //1.将前端提交商品SKU基本信息存入sku_info表
        skuInfoService.save(skuInfo);

        //2.将前端提交当前SKU相关图片集合 存入到sku_image表
        List<SkuImage> skuImageList = skuInfo.getSkuImageList();
        if (!CollectionUtils.isEmpty(skuImageList)) {
            skuImageList.stream().forEach(skuImage -> {
                //2.1 将商品SKU图片关联到SKU
                skuImage.setSkuId(skuInfo.getId());
            });

            //2.2 批量保存商品SKU图片
            skuImageService.saveBatch(skuImageList);
        }

        //3.将前端提交当前SKU的平台属性信息 存入到 sku_attr_value表
        List<SkuAttrValue> skuAttrValueList = skuInfo.getSkuAttrValueList();
        if (!CollectionUtils.isEmpty(skuAttrValueList)) {
            //3.1将平台属性信息关联到SKU
            skuAttrValueList.stream().forEach(skuAttrValue -> {
                skuAttrValue.setSkuId(skuInfo.getId());
            });
            //3.2 批量保存平台属性
            skuAttrValueService.saveBatch(skuAttrValueList);
        }

        //4.将前端提交当前SKU的销售属性信息 存入到 sku_sale_attr_value表
        List<SkuSaleAttrValue> skuSaleAttrValueList = skuInfo.getSkuSaleAttrValueList();
        if (!CollectionUtils.isEmpty(skuSaleAttrValueList)) {
            skuSaleAttrValueList.stream().forEach(skuSaleAttrValue -> {
                //4.1 销售属性关联SPU
                skuSaleAttrValue.setSpuId(skuInfo.getSpuId());
                //4.2 销售属性关联到SKU
                skuSaleAttrValue.setSkuId(skuInfo.getId());
            });
            //4.3 批量保存SKU销售属性信息
            skuSaleAttrValueService.saveBatch(skuSaleAttrValueList);
        }

        //5.将保存的商品SKUID存入布隆过滤器
        RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(RedisConst.SKU_BLOOM_FILTER);
        bloomFilter.add(skuInfo.getId());
    }

    /**
     * 根据三级分类数据获取到skuInfo 列表
     * @param skuInfoPage
     * @param skuInfo
     * @return
     */
    @Override
    public IPage getSkuInfoPage(Page<SkuInfo> skuInfoPage, SkuInfo skuInfo) {
        //1.构建查询条件
        LambdaQueryWrapper<SkuInfo> queryWrapper = new LambdaQueryWrapper<>();
        if (skuInfo.getCategory3Id() != null) {
            queryWrapper.eq(SkuInfo::getCategory3Id, skuInfo.getCategory3Id());
        }
        queryWrapper.orderByDesc(SkuInfo::getUpdateTime);
        //2.执行查询
        return skuInfoMapper.selectPage(skuInfoPage, queryWrapper);
    }

    /**
     * 商品SKU上架
     * @param skuId
     * @return
     */
    @Override
    public void onSale(Long skuId) {
        //1.修改数据库中上架状态
        SkuInfo skuInfo = new SkuInfo();
        skuInfo.setId(skuId);
        skuInfo.setIsSale(1);
        skuInfoService.updateById(skuInfo);
        //2. 将来还需要同步将索引库ES的商品进行上架;需要构建商品缓存到Redis
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_GOODS, MqConst.ROUTING_GOODS_UPPER, skuId);

    }

    /**
     * 商品SKU下架
     * @param skuId
     * @return
     */
    @Override
    public void cancelSale(Long skuId) {
        //SkuInfo skuInfo = skuInfoService.getById(skuId);
        //if (skuInfo != null && skuInfo.getIsSale() != 0) {
        //    skuInfo.setIsSale(0);
        //    skuInfoService.updateById(skuInfo);
        //}
        LambdaUpdateWrapper<SkuInfo> updateWrapper = new LambdaUpdateWrapper<>();
        //1.设置更新条件
        updateWrapper.eq(SkuInfo::getId, skuId);
        //1.设置更新字段值
        updateWrapper.set(SkuInfo::getIsSale, 0);
        skuInfoService.update(updateWrapper);

        //2.将来还需要同步将索引库ES的商品进行下架;需要删除商品缓存Redis
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_GOODS, MqConst.ROUTING_GOODS_LOWER, skuId);
    }


    /**
     * 查询商品信息优化 采用SDR-SprignDataRedis实现分布式锁
     * 1.优先从缓存中获取数据
     * 2.获取分布式锁
     * 3.执行查询数据库逻辑
     *
     * @param skuId
     * @return
     */
    /*@Override
    public SkuInfo getSkuInfoAndImages(Long skuId) {
        try {
            String lockKey = "";
            String lockVal = "";
            //1.优先从Redis分布式缓存中获取业务数据 存在:直接返回  不存在:获取分布式锁
            //1.1 构建商品信息业务数据缓存Key 形式--  sku:24:info
            String dataKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
            //1.2 通过业务Key从分布式缓存中获取业务数据
            SkuInfo skuInfo = (SkuInfo) redisTemplate.opsForValue().get(dataKey);
            if (skuInfo == null) {
                try {
                    //2.尝试获取分布式锁  成功:执行查询数据库逻辑,放入缓存返回 失败:自旋
                    lockKey = "lock";
                    //2.1 构建当前商品锁值 UUID
                    lockVal = UUID.randomUUID().toString().replaceAll("-", "");
                    //2.2 调用set ex nx 获取锁
                    Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, lockVal, RedisConst.SKULOCK_EXPIRE_PX1, TimeUnit.SECONDS);
                    if (flag) {
                        //2.3 执行业务逻辑-从数据库中查询商品信息
                        skuInfo = this.getSkuInfoAndImagesFromDB(skuId);
                        //2.3.1 查询数据库获取业务数据  有值:放入缓存,响应业务数据  没有:null放入缓存(暂存)
                        if (skuInfo == null) {
                            redisTemplate.opsForValue().set(dataKey, skuId, RedisConst.SKUKEY_TIMEOUT, TimeUnit.MILLISECONDS);
                            return skuInfo;
                        }
                        //3.3.2 数据库中有数据,将业务数据放入缓存,返回业务数据
                        redisTemplate.opsForValue().set(dataKey, skuInfo, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                        return skuInfo;
                    } else {
                        try {
                            //本次获取锁失败-自旋
                            Thread.sleep(100);
                            this.getSkuInfoAndImages(skuId);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //3.将锁释放 采用lua脚本释放锁
                    String scriptText = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                            "then\n" +
                            "    return redis.call(\"del\",KEYS[1])\n" +
                            "else\n" +
                            "    return 0\n" +
                            "end";
                    DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                    redisScript.setScriptText(scriptText);
                    redisTemplate.execute(redisScript, Arrays.asList(lockKey), lockVal);
                }
            } else {
                //缓存中业务数据直接返回即可
                return skuInfo;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //兜底处理-查询数据库
        return this.getSkuInfoAndImagesFromDB(skuId);
    }
*/

    /**
     * 查询商品信息优化 采用Redisson实现分布式锁
     * 1.优先从缓存中获取数据
     * 2.获取分布式锁
     * 3.执行查询数据库逻辑
     *
     * @param skuId
     * @return
     */
    /*@Override
    public SkuInfo getSkuInfoAndImages(Long skuId) {
        try {
            //1.优先从分布式缓存Redis 中获取数据
            //1.1 构建商品缓存业务Key 形式: sku:商品ID:info
            String dataKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;

            //1.2 从Redis分布式缓存中获取数据  有值:直接返回 没有:获取分布式锁后执行业务
            SkuInfo skuInfo = (SkuInfo) redisTemplate.opsForValue().get(dataKey);
            if (skuInfo == null) {
                //2.尝试获取分布锁-采用Redisson实现分布式锁
                ///2.1 构建商品锁名称 形式:sku:商品id:lock
                String lockKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKULOCK_SUFFIX;
                RLock lock = redissonClient.getLock(lockKey);
                try {
                    //2.2 创建锁对象,尝试获取锁
                    boolean flag = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX1, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);
                    if (flag) {
                        //2.3 执行查询数据库业务
                        //2.3.1 查询商品信息
                        skuInfo = this.getSkuInfoAndImagesFromDB(skuId);
                        if (skuInfo == null) {
                            //将null对象暂存到redis
                            redisTemplate.opsForValue().set(dataKey, skuInfo, RedisConst.SKUKEY_TIMEOUT, TimeUnit.MILLISECONDS);
                            return skuInfo;
                        } else {
                            //2.3.2 将商品信息放入缓存
                            redisTemplate.opsForValue().set(dataKey, skuInfo, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                            //2.3.3 返回结果
                            return skuInfo;
                        }
                    } else {
                        //获取锁失败,自旋
                        return this.getSkuInfoAndImages(skuId);
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //3.业务执行完毕,将锁释放掉
                    lock.unlock();
                }

            } else {
                //命中缓存数据,直接返回
                return skuInfo;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //兜底操作:查询数据库
        return this.getSkuInfoAndImagesFromDB(skuId);
    }*/


    /**
     * 1、根据SkuID查询SKU商品信息包含图片列表
     * @param skuId
     * @return
     */
    //实现步骤
    //1.根据商品SkuID（主键）查询sku_info表得到商品信息
    //2.根据skuId查询商品图片表sku_image得到商品图片列表
//    public SkuInfo getSkuInfoAndImagesFromDB(Long skuId) {

    @GmallCache(prefix = "sku:", suffix = ":info")
    @Override
    public SkuInfo getSkuInfoAndImages(Long skuId) {
        //1.根据商品SkuID（主键）查询sku_info表得到商品信息
        SkuInfo skuInfo = skuInfoService.getById(skuId);

        //2.根据skuId查询商品图片表sku_image得到商品图片列表
        if (skuInfo != null) {
            LambdaQueryWrapper<SkuImage> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(SkuImage::getSkuId, skuId);
            List<SkuImage> skuImages = skuImageService.list(queryWrapper);
            //封装商品列表
            skuInfo.setSkuImageList(skuImages);
            return skuInfo;
        }
        return null;
    }

    /**
     * 根据SKUID查询商品最新价格
     * @param skuId
     * @return
     */
    // TODO 布隆过滤器 已实现
    @Override
    public BigDecimal getSkuPrice(Long skuId) {
        //1.创建锁对象 获取锁
        String lockKey = RedisConst.SKUKEY_PREFIX + skuId + "price";
        RLock lock = redissonClient.getLock(lockKey);
        try {
            boolean flag = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX1, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);
            if (flag) {
                //2.获取锁成功后执行业务
                LambdaQueryWrapper<SkuInfo> queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.eq(SkuInfo::getId, skuId);
                //指定查询字段
                queryWrapper.select(SkuInfo::getPrice);
                SkuInfo skuInfo = skuInfoService.getOne(queryWrapper);
                if (skuInfo != null) {
                    return skuInfo.getPrice();
                }
            }
            return null;
        } catch (Exception e) {
            log.error("获取价格异常:{}", e);
        } finally {
            //3.将锁释放
            lock.unlock();
        }
        return new BigDecimal("0");
    }

    /**
     * 5、根据SkuID查询当前商品包含平台属性以及属性值
     * @param skuId
     * @return
     */
    @GmallCache(prefix = "attrList:")
    @Override
    public List<BaseAttrInfo> getAttrList(Long skuId) {
        SkuAttrValueMapper skuAttrValueMapper = (SkuAttrValueMapper) skuAttrValueService.getBaseMapper();
        List<BaseAttrInfo> attrList = skuAttrValueMapper.getAttrList(skuId);
        if (!CollectionUtils.isEmpty(attrList)) {
            for (BaseAttrInfo baseAttrInfo : attrList) {
                baseAttrInfo.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
            }
        }
        return attrList;
    }

    /**
     * 其实就是第六步的反向查询
     * 7、获取每一组销售属性对应SkuID组合 {"3736|3738":"24","3736|3739":"25",}
     * @param spuId
     * @return
     */
    @GmallCache(prefix = "skuValueIdsMap:", suffix = ":info")
    @Override
    public String getSkuValueIdsMap(Long spuId) {
        //声明封装所有销售属性跟SKUID对应Map
        HashMap<String, String> mapResult = new HashMap<>();
        //查询自定义SQL
        SkuSaleAttrValueMapper skuSaleAttrValueMapper = (SkuSaleAttrValueMapper) skuSaleAttrValueService.getBaseMapper();
        List<Map> list = skuSaleAttrValueMapper.getSkuValueIdsMap(spuId);
        if (!CollectionUtils.isEmpty(list)) {
            //遍历List获取销售属性以及SKUID
            list.stream().forEach(map -> {
                Long skuId = (Long) map.get("sku_id");
                String valueIds = (String) map.get("value_ids");
                mapResult.put(valueIds, skuId.toString());
            });
        }
        return JSON.toJSONString(mapResult);
    }


}
























